package block

import (
	"time"
	"bytes"
	"encoding/gob"
	"log"
	"crypto/sha256"
)

// ---------------区块------------

/*
	将交易保存到区块链

从现在开始，每个块必须存储至少一笔交易。如果没有交易，也就不可能出新的块。这意味着我们应该移除Block的Data
字段，取而代之的是用交易存储。
*/
// （1）修改区块的定义：移除Data []byte,添加Transactions []*Transaction
type Block struct {
	Timestamp 		int64
	Transactions    []*Transaction
	PreBlockHash 	[]byte
	Hash 			[]byte
	Nonce			int
}

// （2）修改data为交易数据
func NewBlock(transactions []*Transaction,preBlockHash []byte)*Block{
	block := &Block{
		time.Now().Unix(),
		transactions,
		preBlockHash,
		[]byte{},
		0}
	pow := NewProofOfWork(block)
	nonce,hash := pow.Run()

	block.Hash = hash[:]
	block.Nonce = nonce

	return block
}

// 修改创建创世块
func NewGenesisBlock(coinbase *Transaction) *Block  {
	return NewBlock([]*Transaction{coinbase},[]byte{})
}

// 新增：返回块里面的交易哈希,通过哈希提供数据
func (b *Block)HashTransactions()[]byte  {
	var txHashes  	[][]byte
	var txHash		[32]byte

	// 遍历交易
	for _,tx := range b.Transactions{
		txHashes = append(txHashes,tx.ID)
	}

	txHash = sha256.Sum256(bytes.Join(txHashes,[]byte{}))

	return txHash[:]
}

// 序列化Block
func (b *Block)Serialize() []byte {
	// A:定义一个 buffer 存储序列化之后的数据
	/* Buffer是一个实现了读写方法的可变大小的字节缓冲。
	本类型的零值是一个空的可用于读写的缓冲.
	*/
	var result bytes.Buffer
	/*

		// B:初始化一个 gob encoder 并对 block 进行编码，结果作为一个字节数组返回。
	(1)	encoding包
		定义了供其它包使用的可以将数据在字节水平和文本表示之间转换的接口。
	encoding/gob、encoding/json、encoding/xml三个包都会检查使用这些接口。
	因此，只要实现了这些接口一次，就可以在多个包里使用。
	标准包内建类型time.Time和net.IP都实现了这些接口。接口是成对的，分别产生和还原编码后的数据。
	(2)encoding/gob
		gob包管理gobs流 - 在编码器（发送器）和解码器（接收器）之间交换的二进制值。
	一个典型的用途是传输远程过程调用（RPC）的参数和结果，如由“net / rpc”包提供的那些。
	该实现为流中的每种数据类型编译自定义编解码器，并且在使用单个编码器传输值流时缓解编译成本，效率最高。
	（3）func NewEncoder(w io.Writer) *Encoder
	NewEncoder返回一个将在io.Writer上传输的新编码器。
	*/
	encoder := gob.NewEncoder(&result)
	/*
		func (enc *Encoder) Encode(e interface{}) error
		编码传输由空接口值表示的数据项，保证所有必需的类型信息先传送。
	传递一个零指针给编码器会惊慌，因为它们不能通过gob传输。
	*/
	err := encoder.Encode(b)
	if err != nil {
		/*
			log包
			实现了简单的日志服务。本包定义了Logger类型，该类型提供了一些格式化输出的方法。
		本包也提供了一个预定义的“标准”Logger，可以通过辅助函数Print[f|ln]、Fatal[f|ln]和Panic[f|ln]访问，
		比手工创建一个Logger对象更容易使用。Logger会打印每条日志信息的日期、时间，默认输出到标准错误。
		Fatal系列函数会在写入日志信息后调用os.Exit(1)。Panic系列函数会在写入日志信息后panic。
		*/
		log.Panic(err)
	}

	/*
		func (b *Buffer) Bytes() []byte
		返回未读取部分字节数据的切片，len(b.Bytes()) == b.Len()。
	如果中间没有调用其他方法，修改返回的切片的内容会直接改变Buffer的内容。
	*/
	return result.Bytes()
}

/* 解序列化函数：它会接受一个字节数组作为输入，并返回一个 Block。
它不是一个方法（method），而是一个单独的函数（function）
*/
func DeserializeBlock(d []byte) *Block  {
	var block Block

	/*
	gob包：
该包管理gob流——在编码器（发送器）和解码器（接受器）之间交换的binary值。一般用于传递远端程序调用（RemoteProgramCalls-RPC）
的参数和结果，如net/rpc包就有提供。

		func NewReader(b []byte) *Reader
		NewReader创建一个从b读取数据的Reader。
	*/
	decoder := gob.NewDecoder(bytes.NewReader(d))

	/*
		func (dec *Decoder) Decode(e interface{}) error
		Decode从输入流读取下一个之并将该值存入e。如果e是nil，将丢弃该值；
	否则e必须是可接收该值的类型的指针。
	如果输入结束，方法会返回io.EOF并且不修改e（指向的值）。
	*/
	err := decoder.Decode(&block)
	if err != nil {
		log.Panic(err)
	}

	return &block
}
